package com.runa.monitor.platinterface.netservice.equipment;

import com.runa.monitor.comm.dto.*;
import com.runa.monitor.comm.dto.newComm.ValveVO;
import com.runa.monitor.platinterface.backwork.function.TaskFunction;
import com.runa.monitor.platinterface.backwork.impl.RccTaskFunction;
import com.runa.monitor.platinterface.base.BaseContainer;
import com.runa.monitor.platinterface.config.CustomThreadUncaughtExceptionHandler;
import com.runa.monitor.platinterface.netservice.connect.commstatus.CommStatus;
import com.runa.monitor.platinterface.netservice.server.ListenerServer;
import com.runa.persistence.task.TaskTypeEnum;
import com.runa.persistence.util.LogUtil;
import com.runa.protocol.enums.DtuModel;
import com.runa.protocol.enums.ModbusType;
import com.runa.rccprotocol.analysis.NovoconAnalysis;
import com.runa.rccprotocol.dto.AffirmDeny;
import com.runa.rccprotocol.dto.equdata.EquData;
import com.runa.rccprotocol.dto.novocon.NovoconRequestData;
import com.runa.rccprotocol.dto.param.ExtendParam;
import com.runa.rccprotocol.dto.tiangang.TianGangValveRequestData;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author zhuj
 */

@Slf4j
public class NovoconDriver extends NetEqu<String> {
    private ListenerServer listener;

    private static final List<Point> points;

    private static final List<Integer> ids;

    private final String model;

    //注意下发的地方有顺序引用
    static {
        ids = Arrays.asList(432788, 432789, 432802, 432815, 432840, 433218, 433220, 433280, 433294, 433536);
        points = new ArrayList<>();
        points.add(new Point(ids.get(0), "16位整型", "AB", 0));
        points.add(new Point(ids.get(1), "16位整型", "AB", 0));
        points.add(new Point(ids.get(2), "16位整型", "AB", 0));
        points.add(new Point(ids.get(3), "16位整型", "AB", 0));
        points.add(new Point(ids.get(4), "浮点", "ABCD", 0));
        points.add(new Point(ids.get(5), "浮点", "ABCD", 0));
        points.add(new Point(ids.get(6), "浮点", "ABCD", 0));
        points.add(new Point(ids.get(7), "浮点", "ABCD", 0));
        points.add(new Point(ids.get(8), "浮点", "ABCD", 0));
        points.add(new Point(ids.get(9), "32位整型", "ABCD", 0));
    }

    public NovoconDriver(String serial, Integer timeout, String model) {
        super(serial, timeout);
        this.model = model;
    }

    public NovoconDriver(String serial, Integer timeout, String model, ListenerServer listener) {
        super(serial, timeout);
        this.model = model;
        this.listener = listener;
        PlcService plc = new PublicDtuPlcService(-1,-1,
                serial(), null, timeout(), ModbusType.MODBUS_RTU, DtuModel.URG780, null, listener);
        BaseContainer.putPlcService(plc);
    }

    @Override
    public void readNodeEqu(List<Node> nodeList, TaskFunction function, EquData.EquDataType type) {
        if ("DANFOSS".equals(model) && !online()) {
            function.doApply(CommStatus.OffLine, nodeList);
            return;
        }
        switch (type) {
            case METER:
                readHouseMeter(nodeList, function);
                break;
            case VALVE:
                readHouseValve(nodeList, function);
                break;
            case PUBLIC_UNIT:
                novoDriverRunner(() -> readBuildValve(nodeList, function));
                break;
            case TEMPERATURE_PANEL:
                readTemperaturePanel(nodeList, function);
                break;
            default:
        }
    }

    private void readTemperaturePanel(List<Node> nodes, TaskFunction function) {
        NovoconRequestData requestData = new NovoconRequestData(serial());
        for (Node node : nodes) {
            NovoconAnalysis.setShunt(serial() + node.getSerial(), "T");
            if ("Spark".equalsIgnoreCase(node.getModel())) {
                requestData.readTempCtrlOfSpark(node.getSerial());
            } else {
                requestData.readTempCtrl(node.getSerial());
            }
            requestData.setRemain(Collections.singletonList(node));
            sendRequest(requestData, function);
        }
    }

    private void readHouseMeter(List<Node> meters, TaskFunction function) {
        for (Node node : meters) {
            NovoconRequestData requestData = new NovoconRequestData(serial());
            if ("Sonosafe".equals(node.getModel())) {
                requestData.readHeatMeter(node.getSerial());
            }
            if ("Start".equals(node.getModel())) {
                requestData.readHeatMeterSdt(node.getSerial());
            }
            requestData.setRemain(Collections.singletonList(node));
            sendRequest(requestData, function);
        }
    }

    private void readHouseValve(List<Node> valves, TaskFunction function) {
        NovoconRequestData requestData;
        for (Node node : valves) {
            requestData = new NovoconRequestData(serial());
            if ("Beacon".equals(node.getModel())) {
                requestData.readBeacon(node.getSerial());
            } else {
                NovoconAnalysis.setShunt(serial() + node.getSerial(), "V");
                requestData.readTempCtrl(node.getSerial());
            }
            requestData.setRemain(Collections.singletonList(node));
            sendRequest(requestData, function);
        }
    }

    private ExecutorService executor;

    private void novoDriverRunner(Runnable runnable) {
        if (executor == null) {
            executor = Executors.newSingleThreadScheduledExecutor(r -> {
                Thread thread = new Thread(r);
                thread.setName("NovoDriver-"+serial());
                thread.setUncaughtExceptionHandler(new CustomThreadUncaughtExceptionHandler());
                return thread;
            });
        }
        executor.execute(runnable);
    }

    private void readBuildValve(List<Node> buildValves, TaskFunction function) {
        for (Node node : buildValves) {
            if (node != null) {
                PlcService plc = getPlcService(node.getIndex());
                if (plc == null) {
                    plc = new PublicDtuPlcService(node.getIndex(), node.getIndex(),
                            serial(), null, timeout(), ModbusType.MODBUS_RTU, DtuModel.URG780, null, listener);
                    setPlcService(plc);
                }
                try {
                    plc.init(points);
                } catch (Exception e) {
                    log.error("plc service初始化失败", e);
                    return;
                }
                if (plc.session().isOnline()) {
                    log.info("{}  {}  读取成功", serial(), node.getSerial());
                    ValveVO valveVO = new ValveVO();
                    Map<String, Object> res = plc.query(ids);
                    valveVO.setValveSerial(node.getSerial());
                    if (CommStatus.OK.getStatus().equals(res.get("status"))) {
                        Map<Integer, String> data = (Map<Integer, String>) res.get("data");
//                        valveData.setValveComm(true);
                        int type = Integer.parseInt(data.get(ids.get(3)));
                        if (type == 1) {
                            valveVO.setModel(ValveVO.ModelEnum.开度);
                            valveVO.setRemoteSet(Float.parseFloat(data.get(ids.get(7))));
                        } else if (type == 6 || type == 7) {
                            valveVO.setModel(ValveVO.ModelEnum.回温);
                            valveVO.setRemoteSet(Float.parseFloat(data.get(ids.get(4))));
                        }
                        valveVO.setSupplyTemp(Float.parseFloat(data.get(ids.get(5))));
                        valveVO.setReturnTemp(Float.parseFloat(data.get(ids.get(6))));
                        valveVO.setOuputOpen(Float.parseFloat(data.get(ids.get(7))));
                        valveVO.setActOpen((float) (int) Float.parseFloat(data.get(ids.get(8))));
                        valveVO.setReadTime(new Date());
                    } else {
                        log.info("{}  {}  读取失败: {}", serial(), node.getSerial(), res.get("status"));
                        valveVO.setSuccess(false);
                        valveVO.setStatus("FFFF");
                    }
                    function.doApply(CommStatus.OK, Collections.singletonList(valveVO));
//                        waitTime();
                } else {
                    log.info("{}  {}  读取失败", serial(), node.getSerial());
                    function.doApply(CommStatus.OffLine, Collections.singletonList(node));
                }
            }
        }
    }

    @Override
    public void setNodeEquArchive(List<Archive> archives, TaskFunction function) {

    }

    @Override
    public void clearNodeEquArchive(TaskFunction function) {

    }

    @Override
    public void valveOnOffControl(List<Node> valves, boolean ifLock, Integer opening, TaskFunction function) {
        if (!online()) {
            function.doApply(CommStatus.OffLine, valves);
            return;
        }
        for (Node valveT : valves) {
            Valve valve = (Valve) valveT;
            if (valve != null) {
                if ("Beacon".equals(valve.getModel())) {
                    executor.execute(() -> {
                        RccTaskFunction.SyncFunction syncFunction = new RccTaskFunction.SyncFunction(function, function.getPriority());
                        NovoconRequestData requestData = new NovoconRequestData(serial());
                        requestData.beaconSetting(valve.getSerial(), valve.getCaliber());
                        requestData.setRemain(valve);
                        sendRequest(requestData, syncFunction);
                        if (syncFunction.waitResult()) {
                            requestData = new NovoconRequestData(serial());
                            requestData.beaconPosition(valve.getSerial(), opening);
                            requestData.setRemain(valve);
                            syncFunction = new RccTaskFunction.SyncFunction(function, timeout(), function.getPriority());
                            sendRequest(requestData, syncFunction);
                            syncFunction.waitResult();
                        }
                    });
                } else {
                    NovoconRequestData requestData = new NovoconRequestData(serial());
                    requestData.tempCtrlOpen(valve.getSerial(), ifLock, opening);
                    requestData.setRemain(valve);
                    sendRequest(requestData, function);
                }
            }
        }
    }

    public void valveTemperatureLock(List<Node> valves, boolean ifLock, float temperature, TaskFunction function) {
        if (!online()) {
            function.doApply(CommStatus.OffLine, valves);
            return;
        }
        NovoconRequestData requestData;
        for (Node valve : valves) {
            if (valve != null) {
                requestData = new NovoconRequestData(serial());
                if ("Beacon".equals(valve.getModel())) {
                    requestData.beaconTempCtrl(valve.getSerial());
                    sendRequest(requestData, null);
                    requestData.beaconTemperature(valve.getSerial(), temperature);
                    requestData.setRemain(valve);
                    sendRequest(requestData, function);
                } else {
                    requestData.tempCtrlTempLock(valve.getSerial(), ifLock, temperature);
                    requestData.setRemain(valve);
                    sendRequest(requestData, function);
                }
            }
        }
    }

    public void valveTemperatureLimit(List<Node> valves, float up, float down, TaskFunction function) {
        if (!online()) {
            function.doApply(CommStatus.OffLine, valves);
            return;
        }
        NovoconRequestData requestData;
        for (Node valve : valves) {
//            Valve valve = findValve(vSerial);
            if (valve != null && !"Beacon".equals(valve.getModel())) {
                requestData = new NovoconRequestData(serial());
                requestData.tempCtrlTempLimit(valve.getSerial(), up, down);
                requestData.setRemain(valve);
                sendRequest(requestData, function);
            }
        }
    }

    @Override
    public void readNodeRecord(List<Integer> indexes, TaskFunction function) {

    }

    private DtuPlcService getPlcService(Integer id) {
        for (DtuPlcService plcService : BaseContainer.getDtu(serial())) {
            if (plcService.getId().equals(id)) return plcService;
        }
        return null;
    }

    private void setPlcService(PlcService plcService) {
        BaseContainer.putPlcService(plcService);
    }

    @Override
    public void sentPublicUnitRecord(List<Node> valves, Integer model, Map param, TaskFunction function) {
        novoDriverRunner(() -> {
            for (Node valve : valves) {
                if (valve != null) {
                    PlcService plc = getPlcService(valve.getIndex());
                    if (plc == null) {
                        plc = new PublicDtuPlcService(valve.getIndex(), valve.getIndex(),
                                serial(), null, timeout(), ModbusType.MODBUS_RTU, DtuModel.URG780, null, listener);
                        setPlcService(plc);
                    }
                    try {
                        plc.init(points);
                    } catch (Exception e) {
                        log.error("plc service初始化失败", e);
                        break;
                    }
                    if (!plc.session().isOnline()) {
                        function.doApply(CommStatus.OffLine, valve);
                        break;
                    }
                    List<PointValue> pointValues = null;
                    switch (model) {
                        case 1:
                            pointValues = new ArrayList<>();
                            pointValues.add(new PointValue(ids.get(0), "1"));
                            pointValues.add(new PointValue(ids.get(1), "1"));
                            pointValues.add(new PointValue(ids.get(3), "1"));
                            pointValues.add(new PointValue(ids.get(7), param.get("targetValue").toString()));
                            break;
                        case 2:
                        case 3:
                        case 4:
                            break;
                        default:
                    }
                    if (pointValues != null) {
                        function.setStatus(valve);
                        Map<String, Object> res = plc.constructAndSendWriteRequest(pointValues);
                        if (res.get("status").equals(CommStatus.OK.getStatus())) {
                            function.doApply(CommStatus.OK, new AffirmDeny() {
                                @Override
                                public Boolean getData() {
                                    return true;
                                }
                            });
                        } else {
                            function.doApply(CommStatus.OK, new AffirmDeny() {
                                @Override
                                public Boolean getData() {
                                    return false;
                                }
                            });
                        }
                    }
                }
            }
        });
    }

    @Override
    public void sentPublicUnitModel(List<Node> valveSerial, boolean model, TaskFunction function) {

    }

    @Override
    public void sentExtendParam(ExtendParam param, TaskFunction function) {

    }

    @Override
    public void readPublicUnitRecord(Node valveSerial, Integer model, TaskFunction function) {

    }

    @Override
    public void readPublicUnitAndMeter(Node valveSerial, Node meterSerial, TaskFunction function) {

    }

    @Override
    public void heartbeatUpdate() {

    }

    @Override
    public boolean heartStop() {
        return false;
    }

    @Override
    public boolean online() {
        if ("NOVOCON".equals(model)) {
            Optional<DtuPlcService> optional = BaseContainer.getDtu(serial()).stream().findFirst();
            return optional.map(dtuPlcService -> dtuPlcService.session().isOnline()).orElse(false);
        } else {
            return super.online();
        }
    }

    private void waitTime() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {

        }
    }
}
